#include "Scene2d.hpp"
#include "../core/Game.hpp"
#include "../video/Screen.hpp"

#include <algorithm>

#include "Event.hpp"
#include "DrawEvent.hpp"
#include "CollisionEvent.hpp"
#include "KeyboardEvent.hpp"
#include "MouseEvent.hpp"

#include "Entity.hpp"
#include "DrawHandler.hpp"
#include "FrameHandler.hpp"
#include "CollisionHandler.hpp"
#include "KeyboardHandler.hpp"
#include "MouseHandler.hpp"

#include <set>

namespace yep
{

Scene2d::Scene2d()
{
}

Scene2d::~Scene2d()
{
}

void Scene2d::setup( Game &game )
{
    fireSetup( game, !game.isRunning() );
}

void Scene2d::cleanup( Game &game )
{
    fireCleanup( game, !game.isRunning() );
}

static bool orderDepthLast( DrawHandler *right, DrawHandler *left )
{
    double ldepth = left->getDepth();
    double rdepth = right->getDepth();
    return rdepth < ldepth || (!(rdepth > ldepth) && right < left);
}

void Scene2d::logic( Game &game )
{
    Event standardEvent( game, *this );

    // Kill defuncts
    if( _dyingHandlers.size() ) {
        for( auto it = _dyingHandlers.begin(); it != _dyingHandlers.end() ; ++it ) {
            ( *it )->evDestroy( standardEvent );
            _sceneHandlers.erase( find( _sceneHandlers.begin(), _sceneHandlers.end(), *it ) );
        }

        _dyingHandlers.clear();
    }

    //Birth unborns
    if( _unbornHandlers.size() ) {
        for( auto it = _unbornHandlers.begin(); it != _unbornHandlers.end() ; ++it ) {
            ( *it )->evCreate( standardEvent );
            _sceneHandlers.push_back( *it );
        }

        _unbornHandlers.clear();
    }

    //Begin Frame Handling
    for( auto it = _frameHandlers.begin(); it != _frameHandlers.end(); ++it ) {
        ( *it )->evFrameBegin( standardEvent );
    }

    //Input handling
    SDL_Event event;

    while( SDL_PollEvent( &event ) ) {
        switch( event.type ) {
        case SDL_QUIT:
            game.exit();
            break;

            //Key Down handling
        case SDL_KEYDOWN:
        {
            KeyboardEvent keyEvent( game, *this, event.key.keysym );

            for( auto it = _keyboardHandlers.begin(); it != _keyboardHandlers.end(); ++it )
            {
                ( *it )->evKeyDown( keyEvent );

                if( keyEvent.hasVeto() )
                    break;
            }

            if( event.key.keysym.sym == SDLK_ESCAPE ) {
                game.exit();
            }
            break;
        }

        //Key Up handling
        case SDL_KEYUP:
        {
            KeyboardEvent keyEvent( game, *this, event.key.keysym );

            for( auto it = _keyboardHandlers.begin(); it != _keyboardHandlers.end(); ++it )
            {
                ( *it )->evKeyUp( keyEvent );

                if( keyEvent.hasVeto() )
                    break;
            }
            break;
        }

        case SDL_MOUSEMOTION:
        {
            typedef bool( *comparator )( MouseHandler *, MouseHandler * );
            set<MouseHandler *, comparator> selectedToMouseMove( _mouseHandlers.begin(),
                    _mouseHandlers.end(), ( comparator ) orderDepthLast );
            Vector2 newPosition( event.motion.x, event.motion.y );
            Vector2 oldPosition( newPosition );
            oldPosition += {static_cast<double>( -event.motion.xrel ), static_cast<double>( -event.motion.yrel )};
            MouseMoveEvent moveEvent( game, *this, oldPosition, newPosition, event.motion.state );

            for( auto it = selectedToMouseMove.begin(); it != selectedToMouseMove.end(); ++it )
            {
                MouseHandler &handler = **it;

                if( handler.isInside( newPosition ) )
                    if( handler.isInside( oldPosition ) )
                        handler.evMouseMove( moveEvent );
                    else
                        handler.evMouseOver( moveEvent );
                else if( handler.isInside( oldPosition ) )
                    handler.evMouseOut( moveEvent );

                if( moveEvent.hasVeto() )
                    break;
            }

            break;
        }

        case SDL_MOUSEBUTTONDOWN:
        {
            typedef bool( *comparator )( MouseHandler *, MouseHandler * );
            set<MouseHandler *, comparator> selectedToClick( _mouseHandlers.begin(),
                    _mouseHandlers.end(), ( comparator ) orderDepthLast );
            Vector2 position( event.button.x, event.button.y );
            MouseButtonEvent buttonEvent( game, *this, position, event.button.button );

            for( auto it = selectedToClick.begin(); it != selectedToClick.end(); ++it )
            {
                MouseHandler &handler = **it;

                if( handler.isInside( position ) )
                    handler.evMouseDown( buttonEvent );

                if( buttonEvent.hasVeto() )
                    break;
            }
            break;
        }

        case SDL_MOUSEBUTTONUP:
        {
            typedef bool( *comparator )( MouseHandler *, MouseHandler * );
            set<MouseHandler *, comparator> selectedToClick( _mouseHandlers.begin(),
                    _mouseHandlers.end(), ( comparator ) orderDepthLast );
            Vector2 position( event.button.x, event.button.y );
            MouseButtonEvent buttonEvent( game, *this, position, event.button.button );

            for( auto it = selectedToClick.begin(); it != selectedToClick.end(); ++it )
            {
                MouseHandler &handler = **it;

                if( handler.isInside( position ) )
                    handler.evMouseUp( buttonEvent );

                if( buttonEvent.hasVeto() )
                    break;
            }
            break;
        }

        default:
            break;
        }
    }

    //Handling the key pressed events
    Uint8 *keys = SDL_GetKeyState( NULL );

    for( auto it = _keyboardHandlers.begin(); it != _keyboardHandlers.end(); ++it )
    {
        const set<pair<SDLKey, SDLKey>> &keyIntervals( ( *it )->getKeySet() );

        for( auto range = keyIntervals.begin(); range != keyIntervals.end(); ++range )
        {
            for( int key = range->first; key <= range->second; ++key )
            {
                if( keys[key] )
                {
                    KeyboardEvent keyEvent( game, *this, ( SDL_keysym ) {
                        0, ( SDLKey )key
                    } );
                    ( *it )->evKeyPressed( keyEvent );

                    if( keyEvent.hasVeto() )
                        goto BREAK_POINT;
                }
            }

BREAK_POINT:
            break;
        }
    }

    //Frame Handling
    for( auto it = _frameHandlers.begin(); it != _frameHandlers.end(); ++it ) {
        ( *it )->evFrame( standardEvent );
    }

    //Collision Handling
    for( auto it = _collisionHandlers.begin(); it != _collisionHandlers.end(); ++it ) {
        auto jt = it;

        for( ++jt; jt != _collisionHandlers.end() ; ++jt )
        {
            if( ( *it )->collides( **jt ) )
            {
                CollisionEvent it2jt( game, *this, **jt );
                ( *it )->evCollision( it2jt );
                CollisionEvent jt2it( game, *this, **it );
                ( *jt )->evCollision( jt2it );
            }
        }
    }

    //End Frame Handling
    for( auto it = _frameHandlers.begin(); it != _frameHandlers.end(); ++it ) {
        ( *it )->evFrameEnd( standardEvent );
    }
}

static bool orderDepthFirst( DrawHandler *right, DrawHandler *left )
{
    double ldepth = left->getDepth();
    double rdepth = right->getDepth();
    if(rdepth > ldepth)
        return true;
    if(rdepth < ldepth)
        return false;
    return right < left;
//    return rdepth > ldepth || (!(rdepth < ldepth) && right < left);
}

void Scene2d::draw( Game &game )
{
    Screen &screen = Screen::getInstance();
    Vector2 viewTopLeft( Vector2::ZERO );
    Vector2 viewBottomRight( screen.getWidth(), screen.getHeight() );
    set<DrawHandler *, bool( * )( DrawHandler *, DrawHandler * )>selectedToDraw( orderDepthFirst );

    for( auto it = _drawHandlers.begin(); it != _drawHandlers.end() ; ++it ) {
        DrawHandler &handler = **it;
        //
        Vector2 topLeft = handler.getTopLeft();
        Vector2 bottomRight = handler.getBottomRight();

        if( topLeft.x < viewBottomRight.x && topLeft.y < viewBottomRight.y &&
                bottomRight.x > viewTopLeft.x && bottomRight.y > viewTopLeft.y ) {
            selectedToDraw.insert( &handler );
        }
    }

    DrawEvent drawEvent( game, *this, screen, viewTopLeft, viewBottomRight - viewTopLeft );

    for( auto it = selectedToDraw.begin(); it != selectedToDraw.end(); ++it ) {
        ( *it )->evDraw( drawEvent );
    }
}

void Scene2d::add( Entity &handler )
{
//    _sceneHandlers.push_back(&handler);
    _unbornHandlers.push_back( &handler );
}

bool Scene2d::has( Entity &handler )
{
    return count( _sceneHandlers.begin(), _sceneHandlers.end(), &handler ) +
           count( _unbornHandlers.begin(), _unbornHandlers.end(), &handler );
}

void Scene2d::remove( Entity &handler )
{
    _dyingHandlers.push_back( &handler );
//    remove(_sceneHandlers.begin(), _sceneHandlers.end(), &handler );
}

void Scene2d::handleFrame( FrameHandler &handler )
{
    _frameHandlers.push_back( &handler );
}

bool Scene2d::isHandlingFrame( FrameHandler &handler )
{
    return count( _frameHandlers.begin(), _frameHandlers.end(), &handler );
}

void Scene2d::unhandleFrame( FrameHandler &handler )
{
    _frameHandlers.erase( find( _frameHandlers.begin(), _frameHandlers.end(), &handler ) );
}

void Scene2d::handleDraw( DrawHandler &handler )
{
    _drawHandlers.push_back( &handler );
}

bool Scene2d::isHandlingDraw( DrawHandler &handler )
{
    return count( _drawHandlers.begin(), _drawHandlers.end(), &handler );
}

void Scene2d::unhandleDraw( DrawHandler &handler )
{
    _drawHandlers.erase( find( _drawHandlers.begin(), _drawHandlers.end(), &handler ) );
}

void Scene2d::handleCollision( CollisionHandler &handler )
{
    _collisionHandlers.push_back( &handler );
}

bool Scene2d::isHandlingCollision( CollisionHandler &handler )
{
    return count( _collisionHandlers.begin(), _collisionHandlers.end(), &handler );
}

void Scene2d::unhandleCollision( CollisionHandler &handler )
{
    _collisionHandlers.erase( find( _collisionHandlers.begin(), _collisionHandlers.end(), &handler ) );
}

void Scene2d::handleKeyboard( KeyboardHandler &handler )
{
    _keyboardHandlers.push_back( &handler );
}

bool Scene2d::isHandlingKeyboard( KeyboardHandler &handler )
{
    return count( _keyboardHandlers.begin(), _keyboardHandlers.end(), &handler );
}

void Scene2d::unhandleKeyboard( KeyboardHandler &handler )
{
    _keyboardHandlers.erase( find( _keyboardHandlers.begin(), _keyboardHandlers.end(), &handler ) );
}

void Scene2d::handleMouse( MouseHandler &handler )
{
    _mouseHandlers.push_back( &handler );
}

bool Scene2d::isHandlingMouse( MouseHandler &handler )
{
    return count( _mouseHandlers.begin(), _mouseHandlers.end(), &handler );
}

void Scene2d::unhandleMouse( MouseHandler &handler )
{
    _mouseHandlers.erase( find( _mouseHandlers.begin(), _mouseHandlers.end(), &handler ) );
}

void Scene2d::fireSetup( Game &game, bool isFirstScene )
{
    //Birth unborns
    if( _unbornHandlers.size() ) {
        // To avoid elements created on EventHandler::evCreate() starts imediatelly. They have to be initalized on logic.
        _sceneHandlers.assign( _unbornHandlers.begin(), _unbornHandlers.end() );
        _unbornHandlers.clear();
        Event createEvent( game, *this );

        for( auto it = _sceneHandlers.begin(); it != _sceneHandlers.end() ; ++it ) {
            ( *it )->evCreate( createEvent );
            ( *it )->evSceneBegin( createEvent );

            if( isFirstScene ) {
                ( *it )->evGameBegin( createEvent );
            }
        }
    }
}

void Scene2d::fireCleanup( Game &game, bool isLastScene )
{
    // Kill defuncts
    if( _sceneHandlers.size() ) {
        Event destroyEvent( game, *this );

        for( auto it = _sceneHandlers.begin(); it != _sceneHandlers.end() ; ++it ) {
            if( isLastScene ) {
                ( *it )->evGameEnd( destroyEvent );
            }

            ( *it )->evSceneEnd( destroyEvent );
            ( *it )->evDestroy( destroyEvent );
        }

        _dyingHandlers.clear();
        _sceneHandlers.clear();
    }
}

}
